
package physiqueEngine.formes;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.util.ArrayList;

import physiqueEngine.Refreshable;
import physiqueEngine.WorldPanel;
import physiqueEngine.bigMath.Geometry;

/**
 * @author Jean-Simon Garneau
 * @author David Bernard
 * 
 */
public abstract class PhysBody implements Refreshable, Shape
{
    public static final Color DEF_COLOR = Color.BLACK;
    public static final int LINE_AMPLIFICATION = 5;

    protected boolean isFix = false;
    protected double mass = 20;
    protected double dir = 0;
    protected double vel = 0;
    protected double acc = 0;
    protected double height = 0;
    protected double width = 0;

    protected final Point2D.Double LOC;
    protected final Point2D.Double SPD;
    protected final Point2D.Double ACL;

    protected final Dimension DIM;
    protected final WorldPanel PARENT;
    protected final RectangularShape SHAPE;

    protected Color color = null;

    private ArrayList<Point2D.Double> forces = null;

    public PhysBody(WorldPanel parent, RectangularShape shape)
    {
        this.PARENT = parent;
        this.SHAPE = shape;

        this.LOC = new Point2D.Double();
        this.SPD = new Point2D.Double();
        this.ACL = new Point2D.Double();
        this.DIM = new Dimension();

        this.forces = new ArrayList<Point2D.Double>();

        this.setColor(DEF_COLOR);
    }

    public void setLocation(double x, double y)
    {
        this.LOC.setLocation(x, y);
        this.refreshShape();
    }

    public void setDimension(double h, double w)
    {
        this.DIM.setSize(w, h);
        this.refreshShape();
    }

    public void setColor(Color c)
    {
        this.color = c;
    }

    @Deprecated
    public void setSpeed(double spd, double dir)
    {
        this.SPD.x = Math.cos(Math.toRadians(-dir)) * spd;
        this.SPD.y = Math.sin(Math.toRadians(-dir)) * spd;
    }
    
    public void setSpeed(double module, float theta)
    {
        this.setSpeed(Geometry.trigonometricToCartesian(module, theta));
    }
    
    public void setSpeed(Point2D.Double speed)
    {
        this.SPD.setLocation(speed);
    }

    public void setFix(boolean b)
    {
        this.isFix = b;
    }

    public abstract int getShapeId();

    public Color getColor()
    {
        return this.color;
    }

    /**
     * Read all the forces applied to the body and calculate the final force
     * applied to the body
     * 
     * @return Point2D X is the direction of the force and Y is the power
     */
    public Point2D.Double getNetForce()
    {
        Point2D.Double netForce = new Point2D.Double();

        // Browse all the force applied to the body and compile them
        for (Point2D.Double force : this.forces)
        {
            netForce.x += force.x;
            netForce.y += force.y;
        }

        return netForce;
    }

    public void addForce(Point2D.Double f)
    {
        this.forces.add(f);
    }

    public void applyGrav()
    {
        Point2D.Double f = new Point2D.Double();
        f.x = Math.cos(Math.toRadians(-this.PARENT.gravDir)) * this.PARENT.gravity;
        f.y = Math.sin(Math.toRadians(-this.PARENT.gravDir)) * this.PARENT.gravity;
        this.addForce(f);
    }

    public void applySpeed()
    {
        this.SPD.x += this.ACL.x;
        this.SPD.y += this.ACL.y;
    }

    public void applyAcl()
    {
        Point2D.Double f = this.getNetForce();
        this.ACL.x = f.x / mass;
        this.ACL.y = f.y / mass;
    }

    public Line2D getSpeedLine()
    {
        Point2D.Double center = this.getCenterPoint();
        double speedX = center.x + this.SPD.x * PhysBody.LINE_AMPLIFICATION;
        double speedY = center.y + this.SPD.y * PhysBody.LINE_AMPLIFICATION;

        return new Line2D.Double(center.x, center.y, speedX, speedY);
    }

    public Point2D.Double getCenterPoint()
    {
        double centerX = this.LOC.x + (this.DIM.getHeight() / 2);
        double centerY = this.LOC.y + (this.DIM.getWidth() / 2);
        return new Point2D.Double(centerX, centerY);
    }

    public double getVecteur()
    {
        return Geometry.calcPyth(this.LOC.x, this.LOC.y);
    }

    @Override
    public void refresh()
    {
        if (!this.isFix)
        {
            this.applyGrav();
            this.applyAcl();
            this.applySpeed();
        }

        // Update Position
        LOC.x += SPD.x;
        LOC.y += SPD.y;

        this.forces.clear();
        this.refreshShape();
    }

    @Override
    public boolean contains(Point2D p)
    {
        return this.SHAPE.contains(p);
    }

    @Override
    public boolean contains(Rectangle2D rect)
    {
        return this.SHAPE.contains(rect);
    }

    @Override
    public boolean contains(double x, double y)
    {
        return this.SHAPE.contains(x, y);
    }

    @Override
    public boolean contains(double x, double y, double w, double h)
    {
        return this.SHAPE.contains(x, y, w, w);
    }

    @Override
    public java.awt.Rectangle getBounds()
    {
        return this.SHAPE.getBounds();
    }

    @Override
    public Rectangle2D getBounds2D()
    {
        return this.SHAPE.getBounds2D();
    }

    @Override
    public PathIterator getPathIterator(AffineTransform at)
    {
        return this.SHAPE.getPathIterator(at);
    }

    @Override
    public PathIterator getPathIterator(AffineTransform at, double flatness)
    {
        return this.SHAPE.getPathIterator(at, flatness);
    }

    @Override
    public boolean intersects(Rectangle2D r)
    {
        return this.SHAPE.intersects(r);
    }

    @Override
    public boolean intersects(double x, double y, double w, double h)
    {
        return this.SHAPE.intersects(x, y, w, h);
    }

    private void refreshShape()
    {
        this.SHAPE.setFrame(this.LOC, this.DIM);
    }
    
    public abstract void resolveCollision(PhysBody body);
}
